package com.wish233.wiliwilisearch.manager.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.wish233.wiliwiliapi.domain.inter.Searchable;
import com.wish233.wiliwilicommon.common.ErrorCode;
import com.wish233.wiliwilicommon.exception.BusinessException;
import com.wish233.wiliwilisearch.domain.esdto.VideoEsDTO;
import com.wish233.wiliwilisearch.domain.result.SearchResponse;
import com.wish233.wiliwilisearch.domain.result.SearchResponseWithDeepPage;
import com.wish233.wiliwilisearch.manager.SearchManager;
import com.wish233.wiliwilisearch.utils.SearchUtils;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author WHH
 */
@Service
@Slf4j
public class VideoSearchManager implements SearchManager {

    final int MAX_ALLOWED_RESULTS = 1000;
    @Resource
    private ElasticsearchRestTemplate template;

    /**
     * 使用searchAfter方法解决深度分页
     *
     * @param keyWord
     * @param current
     * @param pageSize
     * @param searchAfter
     * @return
     */
    @Override
    public SearchResponseWithDeepPage doSearchByKeywordWithDeepPage(String keyWord,
                                                                    Integer current, Integer pageSize, String searchAfter) {
        // 构造查询条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.should(QueryBuilders.matchQuery("title", keyWord));
        boolQueryBuilder.should(QueryBuilders.matchQuery("description", keyWord));
        boolQueryBuilder.minimumShouldMatch(1);
        // 添加_id作为第二排序字段确保唯一性
        SortBuilder<?> sortBuilder = new FieldSortBuilder("viewCount")
                .order(SortOrder.DESC);
        SortBuilder<?> idSortBuilder = new FieldSortBuilder("_id")
                .order(SortOrder.ASC);
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder()
                .withSorts(sortBuilder, idSortBuilder)
                .withQuery(boolQueryBuilder);
        // 处理searchAfter
        if (StrUtil.isNotBlank(searchAfter)) {
            try {
                byte[] decode = Base64.decode(searchAfter);
                List<Object> searchAfterList = JSONUtil.toList(new String(decode), Object.class);
                queryBuilder.withSearchAfter(searchAfterList);
                // 使用searchAfter时必须从第0页开始
                queryBuilder.withPageable(PageRequest.of(0, pageSize));
            } catch (Exception e) {
                log.error("searchAfter参数解析失败", e);
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数解析失败");
            }
        } else {
            // 普通分页
            queryBuilder.withPageable(PageRequest.of(current - 1, pageSize));
        }
        SearchHits<VideoEsDTO> hits = template.search(queryBuilder.build(), VideoEsDTO.class);
        if (hits.isEmpty()) {
            return SearchUtils.emptySearchResponseWithDeepPage();
        }
        List<Searchable> res = hits.stream()
                .map(hit -> (Searchable) VideoEsDTO.esDTO2VO(hit.getContent()))
                .collect(Collectors.toList());
        // 获取最后一个结果的排序值作为下一次的searchAfter
        String nextSearchAfter = null;
        if (!hits.getSearchHits().isEmpty()) {
            SearchHit<VideoEsDTO> lastHit = hits.getSearchHits().get(hits.getSearchHits().size() - 1);
            Object[] sortValues = lastHit.getSortValues().toArray();
            nextSearchAfter = Base64
                    .encode(JSONUtil.toJsonStr(sortValues).getBytes());
        }
        return new SearchResponseWithDeepPage(res, hits.getTotalHits(), nextSearchAfter);
    }

    @Override
    public SearchResponse doSearchByKeyword(String keyWord, Integer current, Integer pageSize) {
        int maxPage = (int) Math.ceil((double) MAX_ALLOWED_RESULTS / pageSize);
        // 检查是否超出限制
        if (current > maxPage) {
            return SearchUtils.emptySearchResponse();
        }
        //构造查询条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        boolQueryBuilder.should(QueryBuilders.matchQuery("title", keyWord));
        boolQueryBuilder.should(QueryBuilders.matchQuery("description", keyWord));
        //查询标签
        boolQueryBuilder.should(QueryBuilders.termQuery("tags", keyWord));
        boolQueryBuilder.minimumShouldMatch(1);
        //必须过审才能显示
        boolQueryBuilder.must(QueryBuilders.termQuery("status", 1));
        SortBuilder<?> sortBuilder = new FieldSortBuilder("viewCount")
                .order(SortOrder.DESC);
        SortBuilder<?> idSortBuilder = new FieldSortBuilder("_id")
                .order(SortOrder.ASC);
        //分页
        PageRequest pageRequest = PageRequest.of(current - 1, pageSize);
        NativeSearchQuery query = new NativeSearchQueryBuilder()
                .withSorts(sortBuilder, idSortBuilder)
                .withQuery(boolQueryBuilder)
                .withPageable(pageRequest)
                .build();
        SearchHits<VideoEsDTO> hits = template.search(query, VideoEsDTO.class);
        if (hits.isEmpty()) {
            return SearchUtils.emptySearchResponse();
        }
        List<SearchHit<VideoEsDTO>> searchHits = hits.getSearchHits();
        List<Searchable> res = searchHits.stream().map(searchHit -> {
            VideoEsDTO content = searchHit.getContent();
            return (Searchable) VideoEsDTO.esDTO2VO(content);
        }).collect(Collectors.toList());
        return new SearchResponse(res, hits.getTotalHits());
    }
}
